function convert(input) { alert('Error. Contact us at info@convert.town if this keeps happening'); } $("#dpi-group :input[type='radio']").change(function() { let dpi = $(this).val(); let elm = $('input[name="0"]'); if (dpi == 'other') { elm.val(''); elm.show(); setTimeout(function() { elm.focus(); }, 100); } else { elm.hide(); elm.val(dpi); } }); function processFile(blob, fileName) { var reader = new FileReader(); reader.onload = function(e) { var bytes = new Uint8Array(e.target.result); var dpi = parseInt($('input[name="0"]').val()) || 96; function dpiFileName(name) { var dotIdx = name.lastIndexOf('.'); return (dotIdx >= 0 ? name.slice(0, dotIdx) : name) + dpi + (dotIdx >= 0 ? name.slice(dotIdx) : ''); } function setJfifDpi(b, d) { // Find APP0 JFIF segment robustly and update density fields. var p = 2; // after SOI while (p + 9 < b.length && b[p] === 0xFF) { var marker = b[p + 1]; if (marker === 0xDA || marker === 0xD9) break; // SOS/EOI if (marker >= 0xD0 && marker <= 0xD7) { p += 2; continue; } // restart markers var len = (b[p + 2] << 8) | b[p + 3]; if (len < 2 || p + 2 + len > b.length) break; if (marker === 0xE0 && b[p + 4] === 0x4A && b[p + 5] === 0x46 && b[p + 6] === 0x49 && b[p + 7] === 0x46 && b[p + 8] === 0x00) { b[p + 11] = 1; // units = dpi b[p + 12] = (d >> 8) & 0xFF; b[p + 13] = d & 0xFF; // Xdensity b[p + 14] = (d >> 8) & 0xFF; b[p + 15] = d & 0xFF; // Ydensity return b; } p += 2 + len; } // No JFIF APP0 found -> inject one right after SOI var app0 = new Uint8Array([0xFF,0xE0, 0x00,0x10, 0x4A,0x46,0x49,0x46,0x00, 0x01,0x01, 0x01, (d>>8)&0xFF, d&0xFF, (d>>8)&0xFF, d&0xFF, 0x00,0x00]); var out = new Uint8Array(b.length + app0.length); out[0] = 0xFF; out[1] = 0xD8; out.set(app0, 2); out.set(b.slice(2), 2 + app0.length); return out; } // Any JPEG (FF D8 FF) if (bytes[0] === 0xFF && bytes[1] === 0xD8 && bytes[2] === 0xFF) { // Re-encode to normalize, then patch JFIF density robustly. var objUrl = URL.createObjectURL(blob); var img = new Image(); img.onload = function() { var canvas = document.createElement('canvas'); canvas.width = img.naturalWidth; canvas.height = img.naturalHeight; canvas.getContext('2d').drawImage(img, 0, 0); canvas.toBlob(function(jfifBlob) { URL.revokeObjectURL(objUrl); var fr2 = new FileReader(); fr2.onload = function(ev) { var b2 = new Uint8Array(ev.target.result); var outJpg = setJfifDpi(b2, dpi); add_file_output(URL.createObjectURL(new Blob([outJpg], {type:'image/jpeg'})), dpiFileName(fileName)); }; fr2.readAsArrayBuffer(jfifBlob); }, 'image/jpeg', 0.95); }; img.src = objUrl; return; } // PNG if (bytes[0] === 0x89 && bytes[1] === 0x50 && bytes[2] === 0x4E && bytes[3] === 0x47) { var ppm = Math.round(dpi / 0.0254); var modified = new Uint8Array(bytes); var pHYsPos = -1; var idatPos = -1; for (var i = 8; i < modified.length - 12; ) { var len = ((modified[i] << 24) >>> 0) | (modified[i+1] << 16) | (modified[i+2] << 8) | modified[i+3]; var t0 = modified[i+4], t1 = modified[i+5], t2 = modified[i+6], t3 = modified[i+7]; if (t0 === 0x70 && t1 === 0x48 && t2 === 0x59 && t3 === 0x73) pHYsPos = i; if (idatPos < 0 && t0 === 0x49 && t1 === 0x44 && t2 === 0x41 && t3 === 0x54) idatPos = i; i += 12 + len; } if (pHYsPos >= 0) { var d = pHYsPos + 8; modified[d] = (ppm >> 24) & 0xFF; modified[d+1] = (ppm >> 16) & 0xFF; modified[d+2] = (ppm >> 8) & 0xFF; modified[d+3] = ppm & 0xFF; modified[d+4] = (ppm >> 24) & 0xFF; modified[d+5] = (ppm >> 16) & 0xFF; modified[d+6] = (ppm >> 8) & 0xFF; modified[d+7] = ppm & 0xFF; modified[d+8] = 1; var crcInput = modified.slice(pHYsPos + 4, pHYsPos + 17); var crc = calcCRC32(crcInput); modified[pHYsPos+17] = (crc >> 24) & 0xFF; modified[pHYsPos+18] = (crc >> 16) & 0xFF; modified[pHYsPos+19] = (crc >> 8) & 0xFF; modified[pHYsPos+20] = crc & 0xFF; } else { // Insert pHYs chunk before first IDAT when missing var insertAt = idatPos > 0 ? idatPos : 33; // after IHDR fallback var chunk = new Uint8Array(21); // length = 9 chunk[0]=0; chunk[1]=0; chunk[2]=0; chunk[3]=9; // type pHYs chunk[4]=0x70; chunk[5]=0x48; chunk[6]=0x59; chunk[7]=0x73; // data: X ppm, Y ppm, unit=1 (meter) chunk[8] = (ppm >> 24) & 0xFF; chunk[9] = (ppm >> 16) & 0xFF; chunk[10] = (ppm >> 8) & 0xFF; chunk[11] = ppm & 0xFF; chunk[12] = (ppm >> 24) & 0xFF; chunk[13] = (ppm >> 16) & 0xFF; chunk[14] = (ppm >> 8) & 0xFF; chunk[15] = ppm & 0xFF; chunk[16] = 1; var crc2 = calcCRC32(chunk.slice(4, 17)); chunk[17] = (crc2 >> 24) & 0xFF; chunk[18] = (crc2 >> 16) & 0xFF; chunk[19] = (crc2 >> 8) & 0xFF; chunk[20] = crc2 & 0xFF; var out = new Uint8Array(modified.length + chunk.length); out.set(modified.slice(0, insertAt), 0); out.set(chunk, insertAt); out.set(modified.slice(insertAt), insertAt + chunk.length); modified = out; } add_file_output(URL.createObjectURL(new Blob([modified], {type:'image/png'})), dpiFileName(fileName)); return; } // TIFF (little-endian II or big-endian MM) if ((bytes[0] === 0x49 && bytes[1] === 0x49) || (bytes[0] === 0x4D && bytes[1] === 0x4D)) { var le = bytes[0] === 0x49; var r16 = function(b, o) { return le ? (b[o] | (b[o+1] << 8)) : ((b[o] << 8) | b[o+1]); }; var r32 = function(b, o) { if (le) return (b[o] | (b[o+1]<<8) | (b[o+2]<<16) | (b[o+3]<<24)) >>> 0; return ((b[o]<<24) | (b[o+1]<<16) | (b[o+2]<<8) | b[o+3]) >>> 0; }; var w16 = function(b, o, v) { if (le) { b[o]=v&0xFF; b[o+1]=(v>>8)&0xFF; } else { b[o]=(v>>8)&0xFF; b[o+1]=v&0xFF; } }; var w32 = function(b, o, v) { if (le) { b[o]=v&0xFF; b[o+1]=(v>>8)&0xFF; b[o+2]=(v>>16)&0xFF; b[o+3]=(v>>24)&0xFF; } else { b[o]=(v>>24)&0xFF; b[o+1]=(v>>16)&0xFF; b[o+2]=(v>>8)&0xFF; b[o+3]=v&0xFF; } }; // Read original first IFD var ifdOff = r32(bytes, 4); var numEntries = r16(bytes, ifdOff); var nextIfdOff = r32(bytes, ifdOff + 2 + numEntries * 12); // Collect existing entries, stripping any old resolution tags var keepEntries = []; for (var i = 0; i < numEntries; i++) { var ep = ifdOff + 2 + i * 12; var tag = r16(bytes, ep); if (tag !== 282 && tag !== 283 && tag !== 296) { keepEntries.push({ tag: tag, data: bytes.slice(ep, ep + 12) }); } } // Append at end of file: XRes rational (8b) + YRes rational (8b) + rebuilt IFD var xResOff = bytes.length; var yResOff = xResOff + 8; var newIfdOff = yResOff + 8; var newCount = keepEntries.length + 3; var newIfdSize = 2 + newCount * 12 + 4; var output = new Uint8Array(bytes.length + 16 + newIfdSize); output.set(bytes); // Write XRes and YRes rational values (numerator=dpi, denominator=1) w32(output, xResOff, dpi); w32(output, xResOff + 4, 1); w32(output, yResOff, dpi); w32(output, yResOff + 4, 1); // Build new IFD entries for resolution var xResEntry = new Uint8Array(12); w16(xResEntry,0,282); w16(xResEntry,2,5); w32(xResEntry,4,1); w32(xResEntry,8,xResOff); var yResEntry = new Uint8Array(12); w16(yResEntry,0,283); w16(yResEntry,2,5); w32(yResEntry,4,1); w32(yResEntry,8,yResOff); var resUnitEntry = new Uint8Array(12); w16(resUnitEntry,0,296); w16(resUnitEntry,2,3); w32(resUnitEntry,4,1); w16(resUnitEntry,8,2); // Merge and sort all entries by tag (TIFF spec requirement) var allEntries = keepEntries.slice(); allEntries.push({ tag: 282, data: xResEntry }); allEntries.push({ tag: 283, data: yResEntry }); allEntries.push({ tag: 296, data: resUnitEntry }); allEntries.sort(function(a, b) { return a.tag - b.tag; }); // Write rebuilt IFD w16(output, newIfdOff, newCount); for (var i = 0; i < allEntries.length; i++) { output.set(allEntries[i].data, newIfdOff + 2 + i * 12); } w32(output, newIfdOff + 2 + newCount * 12, nextIfdOff); // Point header to new IFD w32(output, 4, newIfdOff); add_file_output(URL.createObjectURL(new Blob([output], {type:'image/tiff'})), dpiFileName(fileName)); return; } alert('DPI change is supported for JPEG, PNG, and TIFF files.'); }; reader.readAsArrayBuffer(blob); } function calcCRC32(data) { var table = []; for (var i = 0; i < 256; i++) { var c = i; for (var j = 0; j < 8; j++) c = (c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1); table[i] = c; } var crc = 0xFFFFFFFF; for (var i = 0; i < data.length; i++) crc = table[(crc ^ data[i]) & 0xFF] ^ (crc >>> 8); return (crc ^ 0xFFFFFFFF) >>> 0; } var _loadedScripts = {}; function loadScriptPromise(url) { if (_loadedScripts[url]) return _loadedScripts[url]; _loadedScripts[url] = new Promise(function (resolve, reject) { var s = document.createElement('script'); s.src = url; s.onload = resolve; s.onerror = reject; document.head.appendChild(s); }); return _loadedScripts[url]; } function replaceAll(find, replace, str) { return str.replace(new RegExp(find, 'g'), replace); } function beautify(str) { var result = ''; var length = str.length; var i = 0; var braceCountLeft = 0; var braceCountRight = 0; var withinQuotes = false; while (i < length) { var c = str[i]; if (c == '"' && (i == 0 || c[i - 1] != '\\')) { // non-escaped quotes withinQuotes = !withinQuotes; } if (!withinQuotes && (c == '}' || c == '{' || c == ',')) { console.log('Start####' + result); // look back and remove carriage returns and whitespace that are already there var resultIndex = result.length - 1; while (resultIndex >= 0 && (result[resultIndex] == ' ' || result[resultIndex] == '\r' || result[resultIndex] == '\n' || result[resultIndex] == '\t')) { resultIndex = resultIndex - 1; result = result.substr(0, resultIndex + 1); console.log('char ' + result[resultIndex] + '-----' + result + 'zzz ' + result.length + ' ' + resultIndex); } if (c == '{') { braceCountLeft++; result += c + '\r' + GetTabs(braceCountLeft - braceCountRight); } else if (c == '}') { braceCountRight++; // precede with carriage return result += '\r' + GetTabs(braceCountLeft - braceCountRight) + c; } else if (c == ',') { result += c + '\r' + GetTabs(braceCountLeft - braceCountRight); } var nextChar = ''; // advance through whitespace and remove carriage returns that are already there while (i < length && (str[i + 1] == ' ' || str[i + 1] == '\r' || str[i + 1] == '\n' || str[i + 1] == '\t')) { i++; } } else { result += str[i]; } i++; } return result; } function GetTabs(count) { var result = ''; for (var i = 0; i < count; i++) { result += ' '; } return result; }